package com.pilhun.parser;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;


public class XmlBeanDefinitionReaderImpl  implements XmlBeanDefinitionReader{
	
	private static final Logger logger = Logger.getLogger(XmlBeanDefinitionReaderImpl.class.getName());
	private String xmlContext;
	private File xmlFile;
	private Charset encoding;
	private ParserTypes parserType;
	private static String EMPTY_BEAN_PATTERN = "<bean\\s*id\\s*=\\s*\"(.*)\"\\s*class\\s*=\\s*\"(.*)\"\\s*/>";
	private static String FILLED_BEAN_PATTERN = "<bean\\s*id\\s*=\\s*\"(.*)\"\\s*class\\s*=\\s*\"(.*)\"\\s*>\\s*((.|\\s)*)\\s*</bean\\s*>";
	private static String CONSTRUCTOR_ARGUMENT_PATTERN = "<constructor-arg\\s*(value|ref)\\s*=\\s*\"(.*)\"\\s*/>";
	private static String PROPERTY_ARGUMENT_PATTERN = "<property\\s*name\\s*=\\s*\"(.*)\"\\s*(value|ref)\\s*=\\s*\"(.*)\"\\s*/>";
	private static Pattern emptyBeanPattern;
	private static Pattern filledBeanPattern;
	private static Pattern constructorArgPattern;
	private static Pattern propertyArgPattern;
	private Matcher emptyBeanMatcher;
	private Matcher filledBeanMatcher;
	
	static {
		ConsoleHandler consoleHandler = new ConsoleHandler();
		logger.addHandler(consoleHandler);
		emptyBeanPattern = Pattern.compile(EMPTY_BEAN_PATTERN);
		filledBeanPattern = Pattern.compile(FILLED_BEAN_PATTERN);
		constructorArgPattern = Pattern.compile(CONSTRUCTOR_ARGUMENT_PATTERN);
		propertyArgPattern = Pattern.compile(PROPERTY_ARGUMENT_PATTERN);
	}
	
	
	public XmlBeanDefinitionReaderImpl(Charset encoding) {
		this.encoding = encoding;
	}

	private void findEmptyBeans (Map<String, BeanObj> map){
		while(emptyBeanMatcher.find()){
			BeanObj beanObj = new BeanObj();
			String beanId = emptyBeanMatcher.group(1).trim();
			String beanClass = emptyBeanMatcher.group(2).trim();
			beanObj.setId(beanId);
			beanObj.setClassName(beanClass);
			map.put(beanId, beanObj);
		}
	}
	private void findFilledBeans (Map<String, BeanObj> map){		
		while(filledBeanMatcher.find()){
			BeanObj beanObj = new BeanObj();
			String beanId = filledBeanMatcher.group(1).trim();
			String beanClass = filledBeanMatcher.group(2).trim();
			beanObj.setId(beanId);
			beanObj.setClassName(beanClass);
			addContent(beanObj, filledBeanMatcher.group(3).trim());
			map.put(beanId, beanObj);
		}
		
	}
	private void addContent (BeanObj beanObj, String contentString){
		Matcher constructorArgMatcher = constructorArgPattern.matcher(contentString);
		Matcher propertyArgMatcher = propertyArgPattern.matcher(contentString);
		while (constructorArgMatcher.find()){
			ArgumentType type = ArgumentType.convert(constructorArgMatcher.group(1).trim());
			String argument = constructorArgMatcher.group(2).trim();
			beanObj.addToConstructorArgs(new ArgumentEntry(argument, type));
		}
		while (propertyArgMatcher.find()){
			String name = propertyArgMatcher.group(1).trim();
			ArgumentType type = ArgumentType.convert(propertyArgMatcher.group(2).trim());
			String argument = propertyArgMatcher.group(3).trim();
			beanObj.addToPropertyArgs(new ArgumentEntry(name, argument, type));
		}
		
	}
	
	public Map<String, BeanObj> createBeanMapDOM (Map<String, BeanObj> beanMap){
		try {
			Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile);
			doc.getDocumentElement().normalize();
			NodeList nodeList = doc.getElementsByTagName("bean");
			for (int beanNum = 0; beanNum < nodeList.getLength(); beanNum++) {  
				Node node = nodeList.item(beanNum); 
				 if (node.getNodeType() == Node.ELEMENT_NODE) {
					 Element beanNode = (Element) node;
					 BeanObj beanObj = new BeanObj();
					 String beanId = beanNode.getAttribute("id");
					 beanObj.setId(beanId);
					 beanObj.setClassName(beanNode.getAttribute("class"));
					 NodeList constructorNodeList = beanNode.getElementsByTagName("constructor-arg");
					 for (int constructorNum = 0; constructorNum < constructorNodeList.getLength(); constructorNum++) {
						 Element constructorElem = (Element) constructorNodeList.item(constructorNum);
						 if (constructorElem.hasAttribute("value")){
							 beanObj.addToConstructorArgs(new ArgumentEntry(constructorElem.getAttribute("value"), ArgumentType.VALUE));
						 } else { 
							 if (constructorElem.hasAttribute("ref")){
								 beanObj.addToConstructorArgs(new ArgumentEntry(constructorElem.getAttribute("ref"), ArgumentType.REFERENCE));
							 }
						 }
					 }
					 NodeList propertyNodeList = beanNode.getElementsByTagName("property");
					 for (int propertyNum = 0; propertyNum < propertyNodeList.getLength(); propertyNum++) {
						 Element propertyElem = (Element) propertyNodeList.item(propertyNum);
						 String propertyName = propertyElem.getAttribute("name");
						 if (propertyElem.hasAttribute("value")){
							 beanObj.addToPropertyArgs(new ArgumentEntry(propertyName, propertyElem.getAttribute("value"), ArgumentType.VALUE));
						 } else { 
							if (propertyElem.hasAttribute("ref")){
								 beanObj.addToPropertyArgs(new ArgumentEntry(propertyName, propertyElem.getAttribute("ref"), ArgumentType.REFERENCE));
						 	}
						 }
					 }
					 beanMap.put(beanId, beanObj);
				 }
			}	
		return beanMap;
		} catch (SAXException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (IOException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (ParserConfigurationException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		}
		return null;
	}
	
	public Map<String, BeanObj> createBeanMap(){
		Map<String, BeanObj> beanMap = new HashMap<String, BeanObj>();
		if  (getParserType() == ParserTypes.DOM){
			return createBeanMapDOM(beanMap);
			
		} else {
			//internal parser
			findEmptyBeans(beanMap);
			findFilledBeans(beanMap);
			return beanMap;
		}
		
	}

	public void loadBeanDefinitions(String fileName) {
		
		byte[] encoded;
		xmlFile = new File(fileName);
		try {
			encoded = Files.readAllBytes(Paths.get(fileName));
			xmlContext = new String(encoded, encoding);
			emptyBeanMatcher = emptyBeanPattern.matcher(xmlContext);
			filledBeanMatcher = filledBeanPattern.matcher(xmlContext);
		} catch (IOException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		}
	}

	public void setValidating(boolean validating) {
		// TODO Auto-generated method stub
		
	}
	
	public ParserTypes getParserType() {
		 return parserType;
		
	}
	public void setParserType(ParserTypes parserType) {
		this.parserType = parserType;		
	}
	
	
}
